pip install xgboost --user


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from xgboost import XGBRegressor
from sklearn.model_selection import cross_val_score, GridSearchCV 
pd.options.display.max_columns = None 
warnings.filterwarnings('ignore')  
%matplotlib inline

# 数据的读取和初步处理
df_train = pd.read_csv('myspace/used_car_train_20200313.csv', sep=' ')
df_test = pd.read_csv('myspace/used_car_testB_20200421.csv', sep=' ')
train = df_train.drop(['SaleID'], axis=1)
test = df_test.drop(['SaleID'], axis=1)

train.head()
 
test.head()

# 查看总览 - 训练集
train.info()


# 查看总览 - 测试集
test.info()


 
train['notRepairedDamage'] = train['notRepairedDamage'].replace('-', np.nan) 
test['notRepairedDamage'] = test['notRepairedDamage'].replace('-', np.nan)
# 转换数据类型
train['notRepairedDamage'] = train['notRepairedDamage'].astype('float64')
test['notRepairedDamage'] = test['notRepairedDamage'].astype('float64')
# 检查是否转换成功
train['notRepairedDamage'].unique(), test['notRepairedDamage'].unique()

# 查看数值统计描述 - 测试集
test.describe()


# 查看数值统计描述 - 训练集
train.describe()



 
train.drop(['seller'], axis=1, inplace=True)
test.drop(['seller'], axis=1, inplace=True)

 
train = train.drop(['offerType'], axis=1)
test = test.drop(['offerType'], axis=1)t
rain.shape, test.shape
 
fig = plt.figure(figsize=(10, 50))
for i in range(len(train.columns)-1):
    fig.add_subplot(10, 2, i+1)
    sns.regplot(train.drop(['price'], axis=1).iloc[:, i], train['price'])
plt.tight_layout()
plt.show()
 
train[train['power'] > 600]['power'].count() 

# 有143个值不合法，需要用别的值替换

test[test['power'] > 600]['power'].count()




 
# 查看各特征与销售价格之间的线性相关系数
train.corr().unstack()['price'].sort_values(ascending=False)
# 在选择需要删除的特征之前，考虑线性相关系数低的。第一步选中系数绝对值小于0.1的特征， 第二步，抛开线性相关系数，从现实角度思考每个特征对售价的影响
# 特征v_2, v_6, v_1, v_14, v_13, v_7：由于是连续型变量，理论上具有数学意义。既然跟售价的线性相关系数极低，为降低噪声，避免过拟合，考虑删去；
# 特征regionCode, brand：并非连续型变量，不具备数学上的可比较性。与售价的线性相关系数低无法说明各自的取值对售价影响不大，保留。
# 特征name：汽车交易名称，训练集共有99662条不重复值，取值不影响售价，删去。
# 特征creatDate：（二手）汽车开始售卖时间，范围在 [20150618, 20160407]，间隔短，且与regDate（汽车注册时间）线性相关系数仅为-0.001293，其取值显然对售价影响很小，删去。

 
train.drop(['v_2', 'v_6', 'v_1', 'v_14', 'v_13', 'v_7', 'name', 'creatDate'], axis=1, inplace=True)
test.drop(['v_2', 'v_6', 'v_1', 'v_14', 'v_13', 'v_7', 'name', 'creatDate'], axis=1, inplace=True)
train.shape, test.shape


# 再次查看各特征与销售价格之间的线性相关系数
train.corr().unstack()['price'].sort_values(ascending=False)
 
train['power'] = train['power'].map(lambda x: train['power'].median() if x > 600 else x)
test['power'] = test['power'].map(lambda x: test['power'].median() if x > 600 else x)
# 检查是否替换成功
train['power'].plot.hist()

test['power'].plot.hist()


 
=# 查看训练集缺失值存在情况
train.isnull().sum()[train.isnull().sum() > 0]


# 查看测试集缺失值存在情况
test.isnull().sum()[test.isnull().sum() > 0]

3.2.1 处理训练集特征model的唯一缺失值
train[train['model'].isnull()]


# model(车型编码)一般与brand, bodyType, gearbox, power有关，选择以上4个特征与该车相同的车辆的model，选择出现次数最多的值
train[(train['brand'] == 37) & 
      (train['bodyType'] == 6.0) & 
      (train['gearbox'] == 1.0) & 
      (train['power'] == 190)]['model'].value_counts()


# 用157.0填充缺失值
train.loc[38424, 'model'] = 157.0
train.loc[38424, :]


# 查看填充结果
train.info()
 
# 看缺失值数量
print(train['bodyType'].isnull().value_counts())
print('\n')
print(test['bodyType'].isnull().value_counts())


# 可见不同车身类型的汽车售价差别还是比较大的，故保留该特征，填充缺失值
# 看看车身类型数量分布
print(train['bodyType'].value_counts())
print('\n')
print(test['bodyType'].value_counts())

# 在两个数据集上，车身类型为0.0（豪华轿车）的汽车数量都是最多，所以用0.0来填充缺失值
train.loc[:, 'bodyType'] = train['bodyType'].map(lambda x: 0.0 if pd.isnull(x) else x)
test.loc[:, 'bodyType'] = test['bodyType'].map(lambda x: 0.0 if pd.isnull(x) else x)

 
# 看缺失值数量
print(train['fuelType'].isnull().value_counts())
print('\n')
print(test['fuelType'].isnull().value_counts())


# 猜想：燃油类型与车身类型相关，如豪华轿车更可能是汽油或电动， 而搅拌车大多是柴油
# 创建字典，保存不同bodyType下， fuelType的众数，并以此填充fuelTyp的缺失值
dict_enu_train, dict_enu_test = {}, {}
for i in [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]:
    dict_enu_train[i] = train[train['bodyType'] == i]['fuelType'].mode()[0]
    dict_enu_test[i] = test[test['bodyType'] == i]['fuelType'].mode()[0]
# 发现dict_enu_train, dict_enu_test是一样的内容

# 开始填充fuelType缺失值
# 在含fuelType缺失值的条目中，将不同bodyType对应的index输出保存到一个字典中
dict_index_train, dict_index_test = {}, {}
for bodytype in [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]:
    dict_index_train[bodytype] = train[(train['bodyType'] == bodytype) & (train['fuelType'].isnull())].index.tolist()
dict_index_test[bodytype] = test[(test['bodyType'] == bodytype) & (test['fuelType'].isnull())].index.tolist()

# 分别对每个bodyTYpe所对应的index来填充fuelType列
for bt, ft in dict_enu_train.items():
    train.loc[dict_index_train[bt], 'fuelType'] = ft  # Pandas推荐使用这种方法来索引/赋值
test.loc[dict_index_test[bt], 'fuelType'] = ft
 
# 看缺失值数量
print(train['gearbox'].isnull().value_counts())
print('\n')
print(test['gearbox'].isnull().value_counts())

# 可见变速箱类型的不同不会显著影响售价，删去测试集中带缺失值的行或许是可行的做法，但为避免样本量减少带来的过拟合，还是决定保留该特征并填充其缺失值
# 看看车身类型数量分布
print(train['gearbox'].value_counts())
print('\n')
print(test['gearbox'].value_counts())


# 训练集
train.loc[:, 'gearbox'] = train['gearbox'].map(lambda x: 0.0 if pd.isnull(x) else x)

# # 对于测试集，为保证预测结果完整性，不能删去任何行。测试集仅有1910个gearbox缺失值，用数量占绝大多数的0.0（手动档）来填充缺失值
test.loc[:, 'gearbox'] = test['gearbox'].map(lambda x: 0.0 if pd.isnull(x) else x)
# 检查填充是否成功
train.info()


test.info()

 
# 看缺失值数量
# 缺失值数量在两个数据集中的占比都不低
print(train['notRepairedDamage'].isnull().value_counts())
print('\n')
print(test['notRepairedDamage'].isnull().value_counts())


# 查看数量分布
print(train['notRepairedDamage'].value_counts())
print('\n')
print(test['notRepairedDamage'].value_counts())


# 查看线性相关系数
train[['notRepairedDamage', 'price']].corr()['price']

# 很奇怪，在整个训练集上有尚未修复损坏的汽车比损坏已修复的汽车售价还要高。考虑到剩余接近20个特征的存在，这应该是巧合
# 为简单化问题，仍使用数量占比最大的0.0来填充所有缺失值
train.loc[:, 'notRepairedDamage'] = train['notRepairedDamage'].map(lambda x: 0.0 if pd.isnull(x) else x)
test.loc[:, 'notRepairedDamage'] = test['notRepairedDamage'].map(lambda x: 0.0 if pd.isnull(x) else x)

# 最后。检查填充结果
train.info()

test.info()

 
rf = RandomForestRegressor(n_estimators=100, max_depth=8, random_state=1) 
xgb = XGBRegressor(n_stimators=150, max_depth=8, learning_rate=0.1, random_state=1)  
gbdt = GradientBoostingRegressor(subsample=0.8, random_state=1)  # subsample小于1可降低方差，但会加大偏差

X = train.drop(['price'], axis=1)
y = train['price']

 
#随机森林
score_rf = -1 * cross_val_score(rf, X, y, scoring='neg_mean_absolute_error',cv=5).mean()  # 取得分均值
print('随机森林模型的平均MAE为：', score_rf)

# XGBoost
score_xgb = -1 * cross_val_score(xgb,X,y,scoring='neg_mean_absolute_error', cv=5).mean()  # 取得分均值
print('XGBoost模型的平均MAE为：', score_xgb)

# 梯度提升树GBDT
score_gbdt = -1 * cross_val_score(gbdt, X,y,scoring='neg_mean_absolute_error', cv=5).mean()  # 取得分均值
print('梯度提升树模型的平均MAE为：', score_gbdt)

 
params = {'n_estimators': [150, 200, 250], 'learning_rate': [0.1],'subsample': [0.5, 0.8]}
model = GridSearchCV(estimator=xgb, param_grid=params,scoring='neg_mean_absolute_error',cv=3)
model.fit(X, y)
# 输出最佳参数
print('最佳参数为：\n', model.best_params_)
print('最佳分数为：\n', model.best_score_)
print('最佳模型为：\n', model.best_estimator_)


 
predictions = model.predict(test)
result = pd.DataFrame({'SaleID': df_test['SaleID'], 'price': predictions})
result.to_csv('downloads/55214/My_submission.csv', index=False)